#ifndef AVALON_QUEUEDSIGNALHANDLER_HPP
#define AVALON_QUEUEDSIGNALHANDLER_HPP
#include <vector>
#include <boost/function.hpp>
#include <boost/noncopyable.hpp>
#include <boost/preprocessor/facilities/empty.hpp>
#include <boost/preprocessor/repetition/enum_params.hpp>
#include <boost/preprocessor/repetition/enum_binary_params.hpp>
#include <boost/preprocessor/repetition/repeat.hpp>
#include <boost/preprocessor/iteration/local.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/tuple/tuple.hpp>
#include <boost/type_traits/remove_pointer.hpp>
#include <boost/type_traits/remove_reference.hpp>
#include "Avalon/Base/Functional.hpp"
#include "Avalon/Base/Out.hpp"
#include "Avalon/Base/Template.hpp"
#include "Avalon/SignalHandling/SignalHandling.hpp"

namespace Avalon {
namespace SignalHandling {

  /*! \class QueuedSignalHandler
   *  \brief Allows for the delayed handling of signals.
   */
  class QueuedSignalHandler : private boost::noncopyable {
    public:

      //! Indicates that one or more signals have been queued.
      typedef boost::function<void ()> QueuedSlot;

      //! Constructs a QueuedSignalHandler.
      QueuedSignalHandler();

      ~QueuedSignalHandler();

      //! Sets the slot called when signals are queued.
      /*!
        \param queuedSlot The slot to call when signals are queued.
      */
      void SetQueuedSlot(const QueuedSlot& queuedSlot);

      //! Dispatches all signals.
      void HandleSignals();

      //! Returns a slot compatible with this signal handler.
      /*!
        \tparam SlotType A function type of the form R (P0, P1, ...)
        \tparam FunctionType A FunctionType compatible with the SlotType.
        \param slot The slot to make compatible with this signal handler.
        \return A slot compatible with this signal handler.
      */
      template<typename SlotType, typename FunctionType>
      typename GetFunctionType<SlotType>::Type GetSlot(
          const FunctionType& slot) {
        return GetSlotImplementation<SlotType>::Invoke(this, slot);
      }

    private:
      class BaseCallback;
      boost::mutex m_mutex;
      QueuedSlot m_queuedSlot;
      std::vector<BaseCallback*> m_slots;
      class BaseCallback {
        public:
          virtual ~BaseCallback();
          virtual void Invoke() = 0;
      };
      template<typename SlotType, int Arity =
        boost::function_traits<SlotType>::arity>
      struct GetSlotImplementation {};

      #define DEFAULT_CALLBACK_PARAMETERS(z, n, q)                             \
        BOOST_PP_COMMA_IF(n) typename A##n = NullType

      #define PARAMETER_TYPES(z, n, q)                                         \
        BOOST_PP_COMMA_IF(n) typename boost::remove_reference<A##n>::type

      #define PARAMETER_NAMES(z, n, q)                                         \
        BOOST_PP_COMMA_IF(n) a##n

      #define SIGNAL_PARAMETER_TYPES(z, n, q)                                  \
        BOOST_PP_COMMA_IF(n) typename                                          \
        boost::function_traits<SlotType>::arg##n_type

      #define CALLBACK_PLACEHOLDERS(z, n, q)                                   \
        BOOST_PP_COMMA_IF(n) boost::arg<n + 1>()

      #define GET_TUPLE_ELEMENTS(z, n, q)                                      \
        BOOST_PP_COMMA_IF(n) boost::get<n>(m_parameters)

      template<BOOST_PP_REPEAT(AVALON_SIGNAL_HANDLING_PARAMETER_COUNT,
        DEFAULT_CALLBACK_PARAMETERS,), typename Dummy = NullType>
      class Callback {};

      #define BOOST_PP_LOCAL_MACRO(n)                                          \
      template<BOOST_PP_ENUM_PARAMS(n, typename A)>                            \
      class Callback<BOOST_PP_ENUM_PARAMS(n, A)> : public BaseCallback {       \
        public:                                                                \
          Callback(const boost::function<void (BOOST_PP_ENUM_PARAMS(n, A))>&   \
              slot BOOST_PP_COMMA_IF(n) BOOST_PP_ENUM_BINARY_PARAMS(n, A, a))  \
              : m_slot(slot),                                                  \
                m_parameters(BOOST_PP_REPEAT(n, PARAMETER_NAMES,)) {}          \
                                                                               \
          virtual void Invoke() {                                              \
            m_slot(BOOST_PP_REPEAT(n, GET_TUPLE_ELEMENTS,));                   \
          }                                                                    \
                                                                               \
        private:                                                               \
          boost::function<void (BOOST_PP_ENUM_PARAMS(n, A))> m_slot;           \
          boost::tuple<BOOST_PP_REPEAT(n, PARAMETER_TYPES,)> m_parameters;     \
      };                                                                       \
                                                                               \
      BOOST_PP_IF(n, (template<BOOST_PP_ENUM_PARAMS(n, typename A)>),)         \
      void Slot(boost::function<void (BOOST_PP_ENUM_PARAMS(n, A))>& slot       \
          BOOST_PP_COMMA_IF(n) BOOST_PP_ENUM_BINARY_PARAMS(n, A, a)) {         \
        boost::lock_guard<boost::mutex> lock(m_mutex);                         \
        m_slots.push_back(new Callback<BOOST_PP_ENUM_PARAMS(n, A)>(slot        \
          BOOST_PP_COMMA_IF(n) BOOST_PP_REPEAT(n, PARAMETER_NAMES, a)));       \
        if(m_slots.size() == 1) {                                              \
          m_queuedSlot();                                                      \
        }                                                                      \
      }                                                                        \
                                                                               \
      template<typename SlotType>                                              \
      struct GetSlotImplementation<SlotType, n> {                              \
        static typename GetFunctionType<SlotType>::Type Invoke(                \
            QueuedSignalHandler* handler, const SlotType& slot) {              \
          return boost::bind(&QueuedSignalHandler::Slot BOOST_PP_IF(n, <,)     \
            BOOST_PP_REPEAT(n, SIGNAL_PARAMETER_TYPES,) BOOST_PP_IF(n, >,),    \
            handler, slot BOOST_PP_COMMA_IF(n)                                 \
            BOOST_PP_REPEAT(n, CALLBACK_PLACEHOLDERS,));                       \
        }                                                                      \
      };

      #define BOOST_PP_LOCAL_LIMITS (0, AVALON_SIGNAL_HANDLING_PARAMETER_COUNT)
      #include BOOST_PP_LOCAL_ITERATE()
      #undef DEFAULT_CALLBACK_PARAMETERS
      #undef PARAMETER_TYPES
      #undef PARAMETER_NAMES
      #undef SIGNAL_PARAMETER_TYPES
      #undef CALLBACK_PLACEHOLDERS
      #undef GET_TUPLE_ELEMENTS
  };
}
}

#endif // AVALON_QUEUEDSIGNALHANDLER_HPP
